#include <pthread.h>
#include "src/GLRenderer.h"
#include "src/ObjLoader/objLoader.h"
#include "src/Radiosity/RadioScene.h"
#include "src/Radiosity/Hemisphere.h"
#include "src/Math/Vector3.h"

typedef enum{
	computingForm,
	reducing
} RenderState;

float angle;
objLoader scene;
RadioScene* rScene;
Hemisphere* hs;
char* sceneFile;
int isPaused = 1;
int isDrawing = 1;
int isDebugDisplay = 0;
Vector3* Colors;
RenderState State;

pthread_t HostThread;

int FormThreadCount = 6;
pthread_t* FormThreads;
int* FormThreadCurrentPatch;
int* FormThreadIDs;
int* FormThreadsPatchCount;
int* FormThreadsPatchStart;
Matrix4* FormThreadOrientations;

void *FormThread_function(void* param){
	int ID = *((int*)param);
	int start = FormThreadsPatchStart[ID];
	int ppt = FormThreadsPatchCount[ID];

	char buff[100];
	sprintf(buff, "Thread: %d - %d\n", ID, ppt);
	write(1, buff, sizeof(char) * strlen(buff));

	for(int i = start; i < start + ppt;){
		if(!isPaused){
			float* center = rScene->GetPatchCenters() + (3 * i);
			Vector3 tangent(rScene->GetPatchTangents() + (3 * i));
			Vector3 normal(rScene->GetPatchNorms() + (3 * i));
			Vector3 biTan = Vector3::cross(normal, tangent);

			FormThreadOrientations[ID] = Matrix4(tangent, normal, biTan);

			rScene->ComputePatchForm(i, hs, FormThreadOrientations[ID]);
			FormThreadCurrentPatch[ID] = i;
			i++;
		}
	}
}

void *hostThread_function(void* param){
	FormThreads = new pthread_t[FormThreadCount];
	FormThreadCurrentPatch = new int[FormThreadCount];
	FormThreadsPatchStart = new int[FormThreadCount];
	FormThreadsPatchCount = new int[FormThreadCount];
	FormThreadIDs = new int[FormThreadCount];
	FormThreadOrientations = new Matrix4[FormThreadCount];

	int patchCount = rScene->GetPatchCount();
	int ppt = patchCount / FormThreadCount;

	for(int i = FormThreadCount; i--;){
		FormThreadIDs[i] = i;
		FormThreadCurrentPatch[i] = 0;

		if(i == 0) ppt = patchCount;
		patchCount -= ppt;
		FormThreadsPatchStart[i] = patchCount;
		FormThreadsPatchCount[i] = ppt;

		pthread_create(&FormThreads[i], NULL, FormThread_function, (void*)(FormThreadIDs + i));
	}

	for(int i = FormThreadCount; i--;){
		pthread_join(FormThreads[i], NULL);
	}

	//exit(1);
	glEnable(GL_DEPTH_TEST);
	for(int i = 0; i < rScene->GetPatchCount(); i++){
		float sum = 0;
		for(int j = 0; j < rScene->GetPatchCount(); j++){
			sum += rScene->GetForm()[i][j];
		}
		for(int j = 0; j < rScene->GetPatchCount(); j++){
			rScene->GetForm()[i][j]/=sum;
		}
		sum = 0;
		for(int j = 0; j < rScene->GetPatchCount(); j++){
			sum += rScene->GetForm()[i][j];
		}

		if(sum > 0.999f){
			printf(".");
			//exit(1);
		}
		else{
			printf("-");
		}
	}
	printf("Saving...");
	rScene->SaveForm("./scene.form");
	printf("Done\n");

	printf("\nScaling done!\n");
	State = reducing;
}

void drawObjScene(){
    for(int i = 0; i < scene.faceCount; i++){
            obj_face* f = scene.faceList[i];
            //printf("\t@%d Verts: %d\n", i, face->vertex_count);
            obj_vector* v1 = scene.vertexList[f->vertex_index[0]];
            obj_vector* v2 = scene.vertexList[f->vertex_index[1]];
            obj_vector* v3 = scene.vertexList[f->vertex_index[2]];
            obj_vector* v4 = scene.vertexList[f->vertex_index[3]];
			obj_vector* norm = scene.normalList[f->normal_index[0]];

            float verts[15] = {
                            (float)v1->e[0], (float)v1->e[1], (float)v1->e[2],
                            (float)v2->e[0], (float)v2->e[1], (float)v2->e[2],
                            (float)v3->e[0], (float)v3->e[1], (float)v3->e[2],
                            (float)v4->e[0], (float)v4->e[1], (float)v4->e[2],
                            (float)v1->e[0], (float)v1->e[1], (float)v1->e[2],
            };

            glUniform4f(
                    glGetUniformLocation(GLRender_LineShader, "uColor"),
                    (float)norm->e[0]/2.0f + 0.5f,
                    (float)norm->e[1]/2.0f + 0.5f,
                    (float)norm->e[2]/2.0f + 0.5f,
                    1.0f
            );

            GLRender_CustomQuad(verts);

            glDrawArrays(GL_LINE_STRIP, 0, 5);
    }
}
/*---------------------------------------------------------------------------*/
void drawPartScene(){
	float line[6] = {
		CameraPosition[0], CameraPosition[1], CameraPosition[2],
		CameraPosition[0] + CameraDir[0] * 10, CameraPosition[1] + CameraDir[1] * 10, CameraPosition[2] + CameraDir[2] * 10};
	GLRender_CustomLine(
		line
	);
	glDrawArrays(GL_LINES, 0, 2);

    for(int i = 0; i < rScene->RootQuads.size(); i++){
    		RootQuad q = rScene->RootQuads[i];

    		Ray r = {
    			{CameraPosition[0], CameraPosition[1], CameraPosition[2]},
				{CameraDir[0], CameraDir[1], CameraDir[2]}
    		};

            glUniform4f(
                    glGetUniformLocation(GLRender_LineShader, "uColor"),
                    (float)q.Normal[0]/2.0f + 0.5f,
                    (float)q.Normal[1]/2.0f + 0.5f,
                    (float)q.Normal[2]/2.0f + 0.5f,
                    1.0f
            );

    		if(State == computingForm && QuadIntersection(q.Verts, q.Normal, r) > 0){
				GLRender_CustomQuadFilled(q.Verts);
				glDrawArrays(GL_TRIANGLES, 0, 6);
    		}
    		else{
            	GLRender_CustomQuad(q.Verts);
           		glDrawArrays(GL_LINE_STRIP, 0, 5);
           	}
    }
}
/*---------------------------------------------------------------------------*/
void drawSubdivided(){
	int pc = rScene->GetPatchCount();
	float** radiosities = rScene->GetRadiosity();
	float** energies = rScene->GetEnergies();
	float** reflectivities = rScene->GetReflectivities();
	float* verts = rScene->GetPatchVerts();
	GLuint colorLoc = glGetUniformLocation(GLRender_LineShader, "uColor");

	for(int i = pc; i--;){ //scene.faceCount
		float r[3] = { radiosities[i][0], radiosities[i][1], radiosities[i][2] };
		float e[3] = { energies[i][0], energies[i][1], energies[i][2] };

		float avg = rScene->CurrentMaxRadiosity[0] + rScene->CurrentMaxRadiosity[1] + rScene->CurrentMaxRadiosity[2];
		avg /= 3;

		if(!isDebugDisplay){
			glUniform4f(
				colorLoc,
				r[0],// / rScene->CurrentMaxRadiosity[0],
				r[1],// / rScene->CurrentMaxRadiosity[1],
				r[2],// / rScene->CurrentMaxRadiosity[2],
				1.0f
			);
		}else{
			glUniform4f(
				colorLoc,
				r[0] / rScene->CurrentMaxRadiosity[0],
				e[0] / rScene->CurrentMaxEnergy[0],
				0.0f,
				1.0f
			);	
		}
		GLRender_CustomQuadFilled(verts + (15 * i));

		glDrawArrays(GL_TRIANGLES, 0, 6);
	}
}
/*---------------------------------------------------------------------------*/
void drawSubdividedWire(){
	int pc = rScene->GetPatchCount();
	float* verts = rScene->GetPatchVerts();
	GLuint colorLoc = glGetUniformLocation(GLRender_LineShader, "uColor");

    for(int i = 0; i < rScene->RootQuads.size(); i++){
    		RootQuad q = rScene->RootQuads[i];

			glUniform4f(
				colorLoc,
				Colors[i][0],
				Colors[i][1],
				Colors[i][2],
				1.0f
			);

		for(int j = q.Index; j < q.End; j++){
			GLRender_CustomQuad(verts + (15 * j));
			glDrawArrays(GL_LINE_STRIP, 0, 5);
		}
	}
}
/*---------------------------------------------------------------------------*/
void Draw(){

		/*GLCam_View = Matrix4::CreateLook(
			Vector3(0, 18, -12.0f),
			Vector3(0, 0 , 0),
			Vector3(0, 1, 0)
		);*/

	GLRender_BindLine(GLRender_LineShader, GLRender_Quad);

	Matrix4 rot;//Matrix4::RotateY(angle+=0.01f);
	glUniformMatrix4fv(
		glGetUniformLocation(GLRender_LineShader, "uModel"),
		1,
		GL_TRUE,
		(GLfloat*)rot.ptr()
	);

	float* verts = rScene->GetPatchVerts();
	float* norms = rScene->GetPatchNorms();
	int pc = rScene->GetPatchCount();
	GLuint colorLoc = glGetUniformLocation(GLRender_LineShader, "uColor");
	GLuint sizeLoc = glGetUniformLocation(GLRender_LineShader, "uSize");

	glUniform1f(sizeLoc, 1.0f);


	// render the hemisphere
	if(State == computingForm){
		if(isDrawing){
			glEnable(GL_DEPTH_TEST);
			//drawObjScene();
			drawSubdividedWire();
			//drawPartScene();
		}

		if(isDrawing){
			/*
			for(int i = hs->CellCount; i--;){
				Cell* c = &hs->GetCells()[i];
				glUniform4f(
					colorLoc,
					0, 0, c->Emission * hs->CellCount, 0.15f
				);
				GLRender_CustomQuadFilled(verts + (15 * c->PatchIndex));
				glDrawArrays(GL_TRIANGLES, 0, 6);
			}*/

			// render hemispheres
			for(int i = FormThreadCount; i--;){
				float* center = rScene->GetPatchCenters() + (3 * FormThreadCurrentPatch[i]);
				float df = 1.0f / (float)FormThreadCount;
				Matrix4 mod = Matrix4::Translate(center[0],center[1],center[2]);
				mod = mod.Multiply(FormThreadOrientations[i]);
				glUniform4f(
					colorLoc,
					i * df, (FormThreadCount - i) * df, 0.0f, 1.0f
				);
				glUniformMatrix4fv(
					glGetUniformLocation(GLRender_LineShader, "uModel"),
					1,
					GL_TRUE,
					(GLfloat*)mod.ptr()
				);

				glUniform1f(sizeLoc, 4.0f);

				GLRender_BindLine(GLRender_LineShader, hs->DisplayHemisphere);
				glDrawArrays(GL_POINTS, 0, hs->CellCount);
			}
			// draw rays
			/*
			Matrix4 ident;
			glUniformMatrix4fv(
				glGetUniformLocation(GLRender_LineShader, "uModel"),
				1,
				GL_TRUE,
				(GLfloat*)ident.ptr()
			);
			glUniform4f(
				colorLoc,
				1.0f, 0.0f, 1.0f, 1.0f
			);
			for(int i = hs->CellCount; i--;){
				Cell* c = &(hs->GetCells()[i]);
				float* inter = rScene->GetPatchCenters() + (c->PatchIndex * 3);

				float start[3] = {0};
				VecAdd(start, center, c->OriPosition);

				float ray[6] = {
					start[0], start[1], start[2],
					inter[0], inter[1], inter[2]
				};
				GLRender_CustomLine(ray);
				glDrawArrays(GL_LINES, 0, 2);
			}*/
		}
		glDisable(GL_DEPTH_TEST);

		// render all the normals
		/*glUniform4f(
			colorLoc,
			1, 0, 0, 0.5f
		);
		GLRender_BindLine(GLRender_LineShader, rScene->DisplayNorms);
		glDrawArrays(GL_LINES, 0, pc * 2);*/
	}
	else if(State == reducing){
		if(isDrawing){
			glEnable(GL_DEPTH_TEST);
			drawSubdivided();
			//drawPartScene();
		}
		if(!isPaused)
			rScene->Reduce();
	}
}
/*---------------------------------------------------------------------------*/
void Keyboard(unsigned char key, int x, int y){
    switch ( key ) {
		  //case 033:
			case 'q':
				exit(1);
		        break;
		    case 'w':
		    	//VecScl(CameraDir.ptr(), 0.1f);
		    	CameraPosition = Vector4::add(CameraPosition, CameraDir);
		    	CameraPosition[3] = 1.0f;

				CameraDir = GLCam_Orientation.Multiply(Vector4(0, 0, 1, 1));
				CameraLook = Vector4::add(CameraDir, CameraPosition);
				//Running = 0;
		        break;
		    case 's':
		    	//VecScl(CameraDir.ptr(), 0.1f);
		    	CameraPosition = Vector4::sub(CameraPosition, CameraDir);
		    	CameraPosition[3] = 1.0f;

				CameraDir = GLCam_Orientation.Multiply(Vector4(0, 0, 1, 1));
				CameraLook = Vector4::add(CameraDir, CameraPosition);
				//Running = 0;
		        break;
		    case 'o':
		    	char buff[256];
		    	sprintf(buff, "%s.rad", sceneFile);
		    	rScene->SaveDisplayData(buff);
		    case 'p':
		    	isPaused = 1;
		    	break;
		    case 'r':
		    	isPaused = 0;
		    	break;
		    case 'd':
		    	isDrawing = 1;
		    	break;
		    case 'h':
		    	isDrawing = 0;
		    	break;
		    case 'z':
		    	isDebugDisplay = 0;
		    	break;
		    case 'x':
		    	isDebugDisplay = 1;
		    	break;
    }
}
/*---------------------------------------------------------------------------*/
void Cleanup(){}
/*---------------------------------------------------------------------------*/
void Init(){
		scene.load(sceneFile); // load the specified file
		rScene = new RadioScene(&scene, 1.25f);//1.0f);
		hs = new Hemisphere(32, 16);

		if(rScene->LoadForm("./scene.form")){
			printf("Form exists...reducing\n");
			State = reducing;
		}
		else
			State = computingForm;

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

		float zero[3] = {2, 0, 0};
		float one[3] = {4, 0, 0};
		float result[3] = {0};

		printf("zero: ");VecPrint(zero);printf("\n");
		printf("one: ");VecPrint(one);printf("\n");
		VecLerp(result, zero, one, 0.5f);
		printf("Test lerp: "); VecPrint(result);printf("\n");

		printf("Radiosity scene created.\n");

		Colors = new Vector3[19];
		for(int i = 0; i < 19; i++)
			Colors[i] = Vector3((random() % 255) / 255.0f, (random() % 255) / 255.0f, (random() % 255) / 255.0f);

		if(State == computingForm)
			pthread_create(&HostThread, NULL, hostThread_function, NULL);
}
/*---------------------------------------------------------------------------*/
int main(int argv, char* argc[]){

	Vector4 x(1.0f, 0.0f, 0.0f, 1.0f);

	Matrix4 m(
		0.0f, 1.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
	);

	Vector4 y = m.Multiply(x);
	printf("< %f, %f, %f >\n", y[0], y[1], y[2]);

	if(argv == 2){
		sceneFile = argc[1];
		angle = 0;

		printf("Creating render context...");

		GLRenderContext creation = {
			800,                // window width
			600,                // window height
			"Simple Radiosity", // window title
			argv,               // cmd line arg count
			argc,               // cmd line args
			// callback functions
			Init,
			Draw,
			Keyboard,
			Cleanup
		};

		printf("created!\n");



		GLRender_Init(creation);

		return 0;

	}
	
	printf("Incorrect number of args\n");
	return 1;
}
